package com.changge.common.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.changge.common.core.utils.JacksonUtil;
import com.changge.common.core.utils.StringUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * Redis配置类
 *
 * @author zhangrongkang
 * @since  2023/3/21
 */
@Configuration
@EnableCaching
@RequiredArgsConstructor
public class RedisConfig implements CachingConfigurer {

    private final CacheProperties cacheProperties;

    /**
     * Redis序列化
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        ObjectMapper objectMapper = JacksonUtil.newObjectMapper();
        // 允许更改底层 VisibilityCheckers 的配置，以更改自动检测的属性类型的详细信息
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        // 如果使用了Lettuce连接工厂则校验连接
        if (redisConnectionFactory instanceof LettuceConnectionFactory lettuceConnectionFactory) {
            lettuceConnectionFactory.setValidateConnection(true);
        }
        // 设置 RedisTemplate 参数
        return getRedisTemplate(redisConnectionFactory, objectMapper);
    }

    /**
     * 设置RedisTemplate序列化参数
     *
     * @param redisConnectionFactory Redis连接工厂
     * @param objectMapper ObjectMapper
     * @return 序列化后的RedisTemplate
     */
    private static RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 通过StringRedisSerializer来序列化Key
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        // 通过GenericJackson2JsonRedisSerializer来序列化Value
        GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        // 设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 确保redisTemplate被正确的初始化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 使用@Cacheable等缓存注解的时候默认缓存的值是二进制的，改成JSON格式
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(RedisTemplate<String, Object> redisTemplate) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        if (cacheProperties.getRedis().getTimeToLive() != null) {
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(cacheProperties.getRedis().getTimeToLive());
        } else {
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofHours(1)); // 缓存失效时间默认改成1小时
        }
        if (!cacheProperties.getRedis().isUseKeyPrefix()) {
            redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix();
        }
        if (redisCacheConfiguration.usePrefix() && StringUtil.isNotEmpty(cacheProperties.getRedis().getKeyPrefix())) {
            redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(cacheProperties.getRedis().getKeyPrefix());
        }
        if (!cacheProperties.getRedis().isCacheNullValues()) {
            redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
        }
        return redisCacheConfiguration;
    }

}